# This module makes one significant assumption about the
# structure of a singly endpoint: That no full path will
# contain the ':' semicolon character. Semicolons here are 
# used to describe the structure of an endpoint. For example,
# when invoking the #endpoint class method:
#
#   endpoint :get, "/services/:service/:endpoint", required: [:access_token]
#
module Singly
  module Endpoint
    attr_reader :path
    attr_reader :options

    def initialize(params={})
      init_path(params)
      init_options(params)
    end

    # The opts argument is for Typhoeus overrides
    def fetch(opts={})
      validate
      Singly::Http.fetch(path, options.merge(opts))
    end

    # Raises an error if any required parameters have not been
    # supplied or if any path components are missing.
    def validate
      Singly::Error.y_u_no?("has all route params: #{path}") { path.include? ":" }
      missing_options = self.class.required_params - options[:query].keys
      Singly::Error.y_u_no?("has required params :#{missing_options.join(', :')}") { missing_options.any? }
      true  
    end

    # String representation of the composed endpoint.
    # Parameter order is deterministic so it can be
    # used as a key in the /multi endpoint.
    def url
      validate
      query_string = options[:query].sort.inject([]) do |queries, param|
        queries << ("#{CGI.escape(param[0].to_s)}=#{CGI.escape(param[1].to_s)}")
      end.join("&")
      url = "#{Singly::Http.base_url}#{path}"
      url += "?#{query_string}" unless query_string.empty?
      url
    end

    private

    # Constructs the path by replacing route components
    # with their corresponding values specified in the
    # params argument.
    # "/profiles/:id@:service" -> "/profiles/12345@twitter"
    def init_path(params)
      route = self.class.route
      route_keys = route.scan(/:(\w+)/).flatten
      @path = route_keys.inject(route) do |path, key|
        value = params.delete(key.to_sym)
        value ? path.sub(":#{key}", CGI.escape("#{value}")) : path
      end
    end

    def init_options(params)
      @options = {
        method: self.class.http_method, 
        timeout: Singly.timeout,
        query: {}
      }
      if [:post, :put].include? @options[:method]
        # Always send credentials in the url
        @options[:query][:access_token] = params.delete(:access_token) if params[:access_token]
        @options[:query][:client_id] = params.delete(:client_id) if params[:client_id]
        @options[:query][:client_id] = params.delete(:client_secret) if params[:client_secret]
        @options[:body] = params
      else
        @options[:query] = params
      end
      @options
    end

    # Use this to pass credentials down the pipe
    # eg: 
    #   def photos(params={})
    #     Singly::Services::Facebook.new(creds.merge(params))
    #   end
    def creds
      [:access_token, :client_id, :client_secret].inject({}) do |creds, cred_key|
        cred = options[:query][cred_key]
        creds[cred_key] = cred if cred
        creds
      end
    end

    def self.included(base)
      base.extend ClassMethods
    end

    module ClassMethods
      def http_method
        @http_method || :get
      end

      def route
        @route || ""
      end

      def required_params
        (@params && @params[:required]) || []
      end

      def optional_params
        (@params && @params[:optional]) || []
      end

      def endpoint(http_method, route, params={})
        @http_method = http_method
        @route = route
        @params = params
      end
    end
  end
end